package org.ecpkn.PAGridSim.core.gridsimEntity.policy.paPolicy.preemptable;

import gridsim.GridSim;
import gridsim.Gridlet;
import gridsim.MachineList;
import gridsim.PE;
import gridsim.PEList;
import gridsim.ResGridlet;
import gridsim.ResGridletList;

import java.util.Calendar;
import java.util.Collections;

import org.ecpkn.PAGridSim.core.gridsimEntity.paMachine.PAMachine;
import org.ecpkn.PAGridSim.core.tasksCreation.Job;
import org.ecpkn.PAGridSim.core.tasksCreation.Task;

/**
 * This class extend PreemptablePAPolicy class and implements partition
 * allocation policy
 */

public abstract class PartitionPreemptablePAPolicy extends PreemptablePAPolicy {

	public static final class Const {
		public static final int TASK_NOTYET_ALLOCATED_TO_MACHINE = -1;
	}

	/**
	 * Allocates a new PartitionPreemptablePAPolicy object
	 * 
	 * @param resourceName
	 *            GridResource name that will contain this allocation policy
	 * @param policyName
	 *            this policy entity name
	 * @throws Exception
	 *             This happens when one of the following scenarios occur:
	 *             creating this entity before initializing GridSim package;
	 *             this entity name is null or empty;this entity has zero number
	 *             of PEs (Processing Elements). No PEs mean the Gridlets can't
	 *             be processed. A GridResource must contain one or more
	 *             Machines. A Machine must contain one or more PEs.
	 * @see gridsim.GridSim#init(int, Calendar, boolean, String[], String[],
	 *      String)
	 * @pre resourceName != null;entityName != null
	 */
	public PartitionPreemptablePAPolicy(String resourceName, String policyName)
			throws Exception {
		super(resourceName, policyName);
	}

	protected void schedule(ResGridlet rgl) {
		// it's a partition policy, a task should by done in a single
		// machine
		Job j = (Job) rgl.getGridlet();
		int taskMachineID = taskAlreadyExecutedInMachine(j);
		if (taskMachineID == Const.TASK_NOTYET_ALLOCATED_TO_MACHINE) {
			// if there is an available machine, then allocate immediately
			if (gridletInExecList_.size() < getAwakePAMachineList().size()) {
				// it should absolutely succeed
				allocatePEtoGridlet(rgl, taskMachineID, this.resource_
						.getMachineWithFreePE().getMachineID());
			} else {
				// if there is no available machine, then decide whether to
				// preempt and which one to preempt
				tryToPreempt(rgl, gridletInExecList_);
			}
		}
		// taskMachineID already assigned
		else {
			if (resource_.getMachineList().getMachine(taskMachineID)
					.getNumFreePE() != 0) {
				// machine free
				allocatePEtoGridlet(rgl, taskMachineID, taskMachineID);
			} else {
				// machine Occupied
				ResGridletList compareList = new ResGridletList();
				for (int i = 0; i < this.gridletInExecList_.size(); i++) {
					if (this.gridletInExecList_.get(i).getMachineID() == taskMachineID) {
						compareList.add(gridletInExecList_.get(i));
						break;
					}
				}
				tryToPreempt(rgl, compareList);
			}
		}
	}

	/**
	 * Allocates the first Gridlet in the Queue list (if any) to execution list
	 */
	protected void allocateQueueAndPauseGridlet(int freeMachineID) {
		// if there are many Gridlets in the QUEUE, then allocate a
		// PE to the first Gridlet in the list since it follows FCFS
		// (First Come First Serve) approach. Then removes the Gridlet from
		// the Queue list
		if (freeMachineID >= this.getAwakePAMachineList().size()) {
			// the machine may have to be show down in the new sample period, do
			// nothing in this case
			return;
		}
		Job j = null;

		ResGridlet obj = null;
		// queue and paused list
		ResGridletList qpList = new ResGridletList();
		// compare the queue list and the paused list at the same time
		qpList.addAll(gridletQueueList_);
		qpList.addAll(gridletPausedList_);

		Collections.sort(qpList, getPriorityComparator());
		boolean success = false;
		int i = 0;
		if ((qpList.size() > 0)) {
			while (j == null && i < qpList.size()) {
				j = (Job) qpList.get(i).getGridlet();
				obj = (ResGridlet) qpList.get(i);
				// allocate the Gridlet into an empty PE slot and remove it
				// from the queue list
				int taskMachineID = taskAlreadyExecutedInMachine(j);
				if ((taskMachineID == freeMachineID || taskMachineID == Const.TASK_NOTYET_ALLOCATED_TO_MACHINE)
						&& gridletQueueList_.contains(obj)) {
					// may be false cause the task should go to another machine
					// for balance,load>1
					success = allocatePEtoGridlet(obj, taskMachineID,
							freeMachineID);
					if (success == true) {
						gridletQueueList_.remove(obj);
						break;
					} else {
						j = null;
						i++;
					}
				} else if (taskMachineID == freeMachineID
						&& gridletPausedList_.contains(obj)) {
					success = resumeGridletFromPause(obj, freeMachineID);
					if (success == true) {
						gridletPausedList_.remove(obj);
						break;
					} else {
						// happen when we don't allow a missing deadline pause
						// gridlet to continue to the end
						j = null;
						i++;
					}
				} else {
					j = null;
					i++;
				}
			}
		}
	}

	/**
	 * Allocates a Gridlet into a free PE and sets the Gridlet status into
	 * INEXEC and PE status into busy afterwards
	 * 
	 * @param rgl
	 *            a ResGridlet object
	 * @param freeMachineID
	 * @return <tt>true</tt> if there is an empty PE to process this Gridlet,
	 *         <tt>false</tt> otherwise
	 * @pre rgl != null
	 */
	protected boolean allocatePEtoGridlet(ResGridlet rgl, int taskMachineID,
			int freeMachineID) {
		// IDENTIFY MACHINE which has a free PE and add this Gridlet to it.
		Job job = (Job) rgl.getGridlet();
		PAMachine myMachine = getMachineForGridlet(taskMachineID,
				freeMachineID, job);

		// If no Machine is empty then ignore the rest
		if (myMachine == null) {
			return false;
		}
		// gets the list of PEs and find one empty PE
		PEList MyPEList = myMachine.getPEList();
		int freePE = MyPEList.getFreePEID();
		// Identify Completion Time and Set Failed if necessary
		int rating = myMachine.getMIPSRating();
		double execuTime = forecastFinishTime(rating,
				rgl.getRemainingGridletLength());
		double roundUpTime = execuTime + GridSim.clock() + 0.001;
		// we need a rounding up here, because if we send the finish signal too
		// early, and actually the job has not yet been finished,it will lost
		// the first chance of finish check and it has to wait for the next one
		// to give a signal.In some cases,the next one come too late,which can
		// cause it to lose its deadline
		if (taskMachineID == Const.TASK_NOTYET_ALLOCATED_TO_MACHINE) {
			// new task to a machine,we should update the machine utilization
			myMachine.changeSingleTaskMachineUtilization(execuTime
					/ job.getPeriode_());
		}
		// should we allow a job to continue when we are sure that it will fail
		// if (job.getDeadline_() < roundUpTime) {
		// rgl.setGridletStatus(Gridlet.FAILED);
		// return false;
		// }
		// ALLOCATE IMMEDIATELY
		rgl.setGridletStatus(Gridlet.INEXEC); // change Gridlet status
		rgl.setMachineAndPEID(myMachine.getMachineID(), freePE);
		// add this Gridlet into execution list
		gridletInExecList_.add(rgl);
		// Set allocated PE to BUSY status
		super.resource_.setStatusPE(PE.BUSY, rgl.getMachineID(), freePE);
		rgl.setFinishTime(roundUpTime);
		// then send this into itself
		super.sendInternalEvent(roundUpTime - GridSim.clock());
		return true;
	}

	/**
	 * Get a machine for the Gridlet(job)
	 * 
	 * @param taskMachineID
	 * @param freeMachineID
	 * @param job
	 * @return
	 */
	private PAMachine getMachineForGridlet(int taskMachineID,
			int freeMachineID, Job job) {
		Task task = job.getTask_();
		PAMachine m = (PAMachine) resource_.getMachineList().getMachine(
				freeMachineID);

		// just want a machine,no limitation of ID
		if (taskMachineID == Const.TASK_NOTYET_ALLOCATED_TO_MACHINE) {
			boolean allow = loadBalanceAllowed(freeMachineID, job);
			if (allow == true) {
				m = this.getAwakePAMachineList().getMachine(freeMachineID);
				m.addTaskToMachine(task);
				return m;
			}
		} else {
			// Generally, the machine for the task is not free, but
			// taskMachineID may equal to freeMachineID
			m = (PAMachine) resource_.getMachineList()
					.getMachine(taskMachineID);
			if (m.getNumFreePE() != 0) {
				return m;
			}
		}
		return null;
	}

	/**
	 * check if the load of the free Machine allows this job to be added this
	 * policy could be changed
	 * 
	 * @param freeMachineID
	 * @param job
	 * @return
	 */
	protected boolean loadBalanceAllowed(int freeMachineID, Job job) {
		MachineList ml = this.resource_.getMachineList();
		PAMachine m = (PAMachine) ml.getMachine(freeMachineID);

		double loadJ = job.getGridletLength()
				/ (m.getFrequencyPE_() * 1000 * job.getPeriode_());
		boolean highload = true;
		for (int i = 0; i < ml.size(); i++) {
			PAMachine machineToBeChecked = (PAMachine) ml.get(i);
			if (machineToBeChecked.isTurnedOn()
					&& machineToBeChecked.getMachinesUtilization() + loadJ <= 1) {
				highload = false;
				break;
			}
		}
		if (m.getMachinesUtilization() + loadJ < 1) {
			return true;
		} else if (highload == true) {
			return true;
		} else {

		}
		// if ((busyMachinesUtilizations.get(busyMachines.indexOf(busyMachines
		// .getMachine(freeMachineID))) - 0.5) < Collections
		// .min(busyMachinesUtilizations)) {
		// return true;
		// }
		return false;
	}

	/**
	 * With partition approach,a task should always be executed in the same
	 * machine(at least in a sample time)
	 * 
	 * @param job
	 * @return Const.TASK_NOTYET_ALLOCATED_TO_MACHIN
	 */
	private int taskAlreadyExecutedInMachine(Job job) {
		int taskID = job.getTaskID_();
		int taskMachineID = Const.TASK_NOTYET_ALLOCATED_TO_MACHINE;

		for (int i = 0; i < this.getAwakePAMachineList().size(); i++) {
			PAMachine m = getAwakePAMachineList().get(i);
			for (int j = 0; j < m.getTasksInMachine().size(); j++) {
				if (taskID == m.getTasksInMachine().get(j).getTaskID_()) {
					taskMachineID = getAwakePAMachineList().get(i)
							.getMachineID();
				}
			}
		}
		return taskMachineID;
	}

}
